# -*- coding: utf-8 -*-
from random import uniform



class DensityGen:
    """This class implements all the functionalities to create probaabilities 
    distributions with some specified parameters
    """
    
    def __init__(self):		#costruttore vuoto
        """Empty constructor"""
        pass
    
    
    def generate(self, n_distr, n_values = 15, n_zeroes = 2, n_overlaps = 4 ):
    	"""Generate a set of distributions, in the form of a list of lists.
    	Each list has the same number of elements and represent a probability space that is, 
    	the sum of all the elements has to be one. The distribution can be partially overlapped,
        meaning that for each index, not all the elements corresponding to that index are
        necessarly different from zero and some elements have equal probability. Approximate number
        of elements with zero probability and number of overlaps can be specified. 
        
    	   @param n_distr: Number of distributions to generate
    	   @param n_values: number of values for each distribution
    	   @param n_zeroes: number of zeroes for each distribution
    	   @param n_overlaps: number of overlaps for each distribution
    	"""
        self.n_distr = n_distr
        
        #sono garantiti n_zeros-n_overlaps zeri e n_overlaps sovrapposizioni
        assert n_zeroes < n_values
        assert n_overlaps < n_values  
        self.distributions = []		#Lista di liste che contiene tutte le distribuzioni
		
        for i_distr in range(n_distr):
            #Creo la lista con tutti i -1
			values = self.__emptyList(n_values)
			
			#Inserisco gli zeri in maniera casuale
			
			zeroes = set()	#insieme delle posizioni degli zeri
			for i in range(n_zeroes): 
				z_idx = int(uniform(0, n_values))		#scelgo una posizione per uno zero in maniera casuale
				while z_idx in zeroes:					#se ho già scelto la posizione
					z_idx = int(uniform(0,n_values))	#ne scelgo un'altra
				zeroes.add(z_idx)						#salvo la posizione
				values[z_idx][1] = 0.0					#inserisco lo zero
				
			
			if (i_distr == 0):		#se sto generando la prima distribuzione
				#assegno valori casuali e riporto tutto nell'intervallo (0;1)
				sum = 0							#somma parziale dei numeri casuali
				for i in range(n_values):		#per tutte le posizioni della lista/distibuzione
					if i not in zeroes:			#se non è una posizione di uno zero
						rnd = int(uniform(1,100))		#scelgo un valore casuale
						sum  += rnd						#ricorda il valore nella somma
						values[i][1] = rnd				#assegna il valore
				#riporto tutto nell'intervallo (0;1)
				for i in range(len(values)):	#per tutte le posizioni della lista/distribuzione
					if i not in zeroes:			#se non è una posizione di uno zero
						values[i][1] = 1.0*values[i][1]/sum		#sostituisci al valore intero la sua percentuale rispetto alla somma
																#per riportare tutto nell'insieme (0;1)
				
            
			else:		#se non sto generando la prima distribuzione
				source_distr = int (uniform(0,i_distr))			# Scelgo la distribuzione da cui prendere le sovrapposizioni in maniera casuale
				source_d = self.distributions[source_distr]
				overlap_sum = 0.0				#somma parziale dei valori sovrapposti
				repeat = True					
				while (repeat):					
					overlaps = set()			#inizializzo l'insieme delle sovrapposizioni
					#scelgo le posizioni delle sovrapposizioni in maniera casuale
					for i in range(n_overlaps):		
						i_overlap = int(uniform(0,n_values))	#scelgo una posizione per le sovrapposizioni in maniera casuale
						while i_overlap in zeroes or i_overlap in overlaps:		#se ho già scelto la posizione o è quella di uno zero
							i_overlap = int(uniform(0,n_values))				#la riscelgo
						overlaps.add(i_overlap)							#inserisco la sovrapposizione nell'insieme delle sovrapposizioni
						values[i_overlap][1] = source_d[i_overlap][1]	#prelevo il valore dalla distribuzione originale
						overlap_sum += values[i_overlap][1]				#aggiungo il valore alla somma parziale 
					
					if (overlap_sum >= 1):			#se ho già superato 1 con le sovrapposizioni non va bene e devo ripetere tutto
						overlap_sum = 0.0			#azzero la somma parziale
					else:
						repeat = False				#non ho superato 1 e quindi posso andare avanti nell'assegnazione dei valori
					
					
				#assegno i restanti valori oltre alla distribuzione
				sum = 0				#somma parziale delle "non sovrapposizioni"
				
				for i in range(n_values):							#per ogni valore della distribuzione
					if i not in overlaps and values[i][1] != 0:		#se non è una sovrapposizione e non è uno zero
						values[i][1] = int(uniform(1,100))			#assegna un valore casuale
						sum += values[i][1]							#aggiorna la somma parziale dei valori
				#riporto nell'intervallo (0;1) i restanti valori
				for i in range(n_values):
					if i not in overlaps and values[i][1] != 0:
						values[i][1] = (1-overlap_sum)*values[i][1]/sum
						
			#Trasformo la distribuzione in un dizionario per comodità con le operazioni successive
	#		dict = self.dictionariz(values)
			self.distributions.append(values)  #inserisco la distribuzione nella lista delle distribuzioni



    def printDistribution(self, index):
    	"""Print the distribution stored at position @see: index.
    	
    	@param index: Position of the distribution. 
    	"""
        if index < len(self.distribution):
            distr=  self.distributions[index]
            for i in range(len(distr)):
			     print distr[i][0], '-->' ,distr[i][1]
        else:
            print "Index is out of range"
            

    def getDistribution(self, index):
		
		if index >= self.n_distr:
			raise IndexError 
		else:
			return self.distributions[index]
			
			

    def getDictDistribution(self, index):
		
		if index < 0 or index >= self.n_distr:
			raise IndexError
		else:
			distr=  self.distributions[index]
			dict = {}
			for i in range(len(distr)):
				dict[distr[i][0]] = distr[i][1]
			return dict


    def getAllDistributions(self):
        return self.distributions

    def __emptyList(self, n_values):
        a = []
        for i in range(n_values):
            a.append([i, -1])
        return a


    def getProbability(self, index):
        ret = []
        for i in self.distributions[index]:
            ret.append(i[1])
        return ret
    
    def getAllProbabilities(self):
        ret =[]
        for i in range(len(self.distributions)):
            ret.append(self.getProbability(i))
        return ret



#ESEMPIO DI UTILIZZO:
#
#d = DensityGen()
#d.generate(4)
#d.printDistribution(0)
#d.printDistribution(1)
#d.printDistribution(2)
#d.printDistribution(3)

